package connection

import (
	"fmt"
	"go.uber.org/zap"
	gsePacket "gse/transport/udp/packet"
	"net"
)

type UDPPacketReceiver struct {
	closed             chan bool
	packetSize         uint16
	readDataBufferPool chan []byte
	conn               *net.UDPConn
	packetChan         chan gsePacket.UdpPacket
	obPacketChan       chan *gsePacket.PacketHeader
	logger             *zap.Logger
	onConnErr          func(error)
}

func NewUDPPacketReceiver(closeChan chan bool, packetSize uint16, readDataBufferPool chan []byte, udpConn *net.UDPConn, packetChan chan gsePacket.UdpPacket, obPacketChan chan *gsePacket.PacketHeader, logger *zap.Logger) *UDPPacketReceiver {
	return &UDPPacketReceiver{
		closed:             closeChan,
		packetSize:         packetSize,
		readDataBufferPool: readDataBufferPool,
		conn:               udpConn,
		packetChan:         packetChan,
		obPacketChan:       obPacketChan,
		logger:             logger,
	}
}
func (r *UDPPacketReceiver) getReadDataBuffer() (b []byte) {
	select {
	case b = <-r.readDataBufferPool:
		return b
	default:
		return make([]byte, r.packetSize)
	}
}

func (r *UDPPacketReceiver) releaseReadDataBuffer(buf []byte) {
	select {
	case r.readDataBufferPool <- buf:
	default:
		buf = nil
	}

}

func (r *UDPPacketReceiver) RecvUDPPacket() {
	for {
		select {
		case <-r.closed:
			return
		default:
			buf := r.getReadDataBuffer()
			n, addr, err := r.conn.ReadFrom(buf)
			if err != nil {
				r.releaseReadDataBuffer(buf)
				if r.onConnErr != nil {
					r.onConnErr(err)
				}
				continue
			}

			data := make([]byte, n)
			copy(data, buf[0:n])
			r.releaseReadDataBuffer(buf)
			packetI, err := gsePacket.NewPacket(data, addr)
			if err != nil {
				r.logger.Warn("client send invalid packet:",
					zap.String("client_addr", addr.String()),
					zap.String("data", fmt.Sprintf("%v", data)),
					zap.Error(err),
				)
				continue
			}
			r.logger.Debug("read remote data packet", zap.Any("type", packetI.GetType()),
				zap.Int("payload_length", len(packetI.GetPayload())),
				zap.Uint16("seq", packetI.GetSeq()),
				zap.String("remote_addr", addr.String()),
				zap.String("data", fmt.Sprintf("%v", data)),
			)

			success := true

			if p, ok := packetI.(*gsePacket.PacketHeader); ok && (p.GetType() == gsePacket.PacketTypePing || p.GetType() == gsePacket.PacketTypeAck || p.GetType() == gsePacket.PacketTypeReset || p.GetType() == gsePacket.PacketTypeClose) {
				select {
				case r.obPacketChan <- p:
				default:
					success = false
				}

				if p.GetType() == gsePacket.PacketTypeReset || p.GetType() == gsePacket.PacketTypeClose {
					select {
					case r.packetChan <- packetI:
					default:
						success = false
					}
				}

			} else {
				select {
				case r.packetChan <- packetI:
				default:
					success = false
				}
			}
			if !success {
				r.logger.Warn("conn data recv chan is full, new packet will be dropped!",
					zap.Any("packet_type", packetI.GetType()),
					zap.Uint16("seq", packetI.GetSeq()),
					zap.String("remote_addr", packetI.GetRemoteAddr().String()),
					zap.Int("current_data_chan_cap", cap(r.packetChan)),
					zap.Int("current_ack_chan_cap", cap(r.obPacketChan)),
				)
			} else {
				if packetI.NeedAck() {
					n, err := r.conn.WriteTo(gsePacket.NewAck(packetI.GetSeq()), addr)
					r.logger.Debug("sending ack:", zap.Uint16("seq", packetI.GetSeq()), zap.String("remote_addr", addr.String()))
					if err != nil {
						r.logger.Warn("failed to send ack", zap.Uint16("seq", packetI.GetSeq()), zap.String("remote_addr", addr.String()))
					} else if n != 2 {
						r.logger.Warn("failed to send complete ack data", zap.Uint16("seq", packetI.GetSeq()), zap.String("remote_addr", addr.String()))
					}

				}
			}
		}
	}
}
